﻿#region Includes
using System;
using OpenTK;
using OpenTK.Graphics.OpenGL;
#endregion

namespace GingerGL.Math
{
    //------------------------------------------------------------//
    /// <summary>
    /// View Frustum
    /// </summary>
    public class Frustum
    {
        //--------------------------------------------------------//
        #region Variables
        /// <summary>
        /// The maximum number of planes in the frustum
        /// </summary>
        public const int MaxPlanes = 6;
        #endregion
        //--------------------------------------------------------//
        #region Properties
        /// <summary>
        /// Gets/Sets TRUE to enable frustum update
        /// </summary>
        public bool Enabled { get; set; }
        /// <summary>
        /// Gets Array of Planes
        /// </summary>
        public Plane[] Planes { get; protected set; }
        /// <summary>
        /// Gets Bottom Plane
        /// </summary>
        public Plane Bottom { get { return this.Planes[(int)FrustumPlanes.Bottom]; } }
        /// <summary>
        /// Gets Far Plane
        /// </summary>
        public Plane Far { get { return this.Planes[(int)FrustumPlanes.Far]; } }
        /// <summary>
        /// Gets Left Plane
        /// </summary>
        public Plane Left { get { return this.Planes[(int)FrustumPlanes.Left]; } }
        /// <summary>
        /// Gets Near Plane
        /// </summary>
        public Plane Near { get { return this.Planes[(int)FrustumPlanes.Near]; } }
        /// <summary>
        /// Gets Right Plane
        /// </summary>
        public Plane Right { get { return this.Planes[(int)FrustumPlanes.Right]; } }
        /// <summary>
        /// Gets Top Plane
        /// </summary>
        public Plane Top { get { return this.Planes[(int)FrustumPlanes.Top]; } }
        /// <summary>
        /// Indexer for Frustum
        /// </summary>
        public Plane this[int index] { get { return this.Planes[index]; } }
        /// <summary>
        /// Indexer for Frustum
        /// </summary>
        public Plane this[FrustumPlanes plane] { get { return this.Planes[(int)plane]; } }
        #endregion
        //--------------------------------------------------------//
        #region Constructor
        /// <summary>
        /// Initializes a new inastance of object
        /// </summary>
        public Frustum() { this.Planes = new Plane[MaxPlanes]; this.Enabled = false; }
        #endregion
        //--------------------------------------------------------//
        #region Methods
        /// <summary>
        /// Updates Frustum with current view
        /// </summary>
        protected void UpdateFrustum()
        {
            if (!this.Enabled) return;

            Matrix4 projection, view;
            Matrix4 clip;

            //get matrices
            GL.GetFloat(GetPName.ProjectionMatrix, out projection);
            GL.GetFloat(GetPName.ModelviewMatrix, out view);

            //Multiply the matrices
            clip = projection * view;

            //calculate mPlanes
            this.Planes[(int)FrustumPlanes.Right].Normal.X = clip.M14 - clip.M11;
            this.Planes[(int)FrustumPlanes.Right].Normal.Y = clip.M24 - clip.M21;
            this.Planes[(int)FrustumPlanes.Right].Normal.Z = clip.M34 - clip.M31;
            this.Planes[(int)FrustumPlanes.Right].D = clip.M44 - clip.M41;

            this.Planes[(int)FrustumPlanes.Left].Normal.X = clip.M14 + clip.M11;
            this.Planes[(int)FrustumPlanes.Left].Normal.Y = clip.M24 + clip.M21;
            this.Planes[(int)FrustumPlanes.Left].Normal.Z = clip.M34 + clip.M31;
            this.Planes[(int)FrustumPlanes.Left].D = clip.M44 + clip.M41;

            this.Planes[(int)FrustumPlanes.Bottom].Normal.X = clip.M14 + clip.M12;
            this.Planes[(int)FrustumPlanes.Bottom].Normal.Y = clip.M24 + clip.M22;
            this.Planes[(int)FrustumPlanes.Bottom].Normal.Z = clip.M34 + clip.M32;
            this.Planes[(int)FrustumPlanes.Bottom].D = clip.M44 + clip.M42;

            this.Planes[(int)FrustumPlanes.Top].Normal.X = clip.M14 - clip.M12;
            this.Planes[(int)FrustumPlanes.Top].Normal.Y = clip.M24 - clip.M22;
            this.Planes[(int)FrustumPlanes.Top].Normal.Z = clip.M34 - clip.M32;
            this.Planes[(int)FrustumPlanes.Top].D = clip.M44 - clip.M42;

            this.Planes[(int)FrustumPlanes.Far].Normal.X = clip.M14 - clip.M13;
            this.Planes[(int)FrustumPlanes.Far].Normal.Y = clip.M24 - clip.M23;
            this.Planes[(int)FrustumPlanes.Far].Normal.Z = clip.M34 - clip.M33;
            this.Planes[(int)FrustumPlanes.Far].D = clip.M44 - clip.M43;

            this.Planes[(int)FrustumPlanes.Near].Normal.X = clip.M14 + clip.M13;
            this.Planes[(int)FrustumPlanes.Near].Normal.Y = clip.M24 + clip.M23;
            this.Planes[(int)FrustumPlanes.Near].Normal.Z = clip.M34 + clip.M33;
            this.Planes[(int)FrustumPlanes.Near].D = clip.M44 + clip.M43;

            //normalize mPlanes
            for (int i = 0; i < 6; ++i)
                this.Planes[i].Normalize();
        }
        /// <summary>
        /// is a bounding box in the frustum?
        /// </summary>
        /// <param name="vertices">Bounding Box Array</param>
        /// <returns>TRUE if points are behind the frustum</returns>
        public bool IsBoundingBoxInside(Vector3[] vertices)
        {
            if (vertices.Length != 8) throw new InvalidProgramException("Incorrect number of vertices");
            //loop through planes
            for (int i = 0; i < 6; ++i)
            {
                //if a point is not behind this plane, try next plane
                if (this.Planes[i].Intersects(vertices[0]) != PlaneIntersectionType.Back) continue;
                if (this.Planes[i].Intersects(vertices[1]) != PlaneIntersectionType.Back) continue;
                if (this.Planes[i].Intersects(vertices[2]) != PlaneIntersectionType.Back) continue;
                if (this.Planes[i].Intersects(vertices[3]) != PlaneIntersectionType.Back) continue;
                if (this.Planes[i].Intersects(vertices[4]) != PlaneIntersectionType.Back) continue;
                if (this.Planes[i].Intersects(vertices[5]) != PlaneIntersectionType.Back) continue;
                if (this.Planes[i].Intersects(vertices[6]) != PlaneIntersectionType.Back) continue;
                if (this.Planes[i].Intersects(vertices[7]) != PlaneIntersectionType.Back) continue;

                //All vertices of the box are behind this plane
                return false;
            }
            return true;
        }
        #endregion
        //--------------------------------------------------------//
    }
    //------------------------------------------------------------//
}
